/*
 * SIT_StdDialog.c : predefined dialog from comdlg32.lib: file, color, font and
 * directory (handled through SHBrowseForFolder). Search'n replace is useless.
 *
 * Written by T.Pierron, Nov 2009.
 */

#include "SIT_P.h"
#include <stdio.h>
#include "graphics_P.h"

	// Tags are not derived from widget, since most of them are useless
	TagList FileDialogClass[] = {
		{ SIT_Title,      "title",      CSG, SIT_STR, OFFSET(SIT_Widget, sw_Title) },
		{ SIT_Filters,    "filters",    C__, SIT_STR, OFFSET(SIT_FileDialog, sf_Filters) },
		{ SIT_SelFilter,  "selFilter",  _SG, SIT_INT, OFFSET(SIT_FileDialog, sf_SelFilter) },
		{ SIT_InitPath,   "initPath",   _SG, SIT_STR, OFFSET(SIT_FileDialog, sf_InitPath) },
		{ SIT_DlgFlags,   "dlgFlags",   _SG, SIT_INT, OFFSET(SIT_FileDialog, sf_Flags) },
		{ SIT_SaveState,  "saveState",  _SG, SIT_PTR, OFFSET(SIT_FileDialog, sf_SaveState) },
		{ SIT_SelPath,    "selPath",    __G, SIT_PTR, OFFSET(SIT_FileDialog, sf_FilePtr) },
		{ SIT_NbSelect,   "nbSelect",   __G, SIT_INT, OFFSET(SIT_FileDialog, sf_NbSelect) },
		{ SIT_TagEnd }
	};

	TagList ColorDialogClass[] = {
		{ SIT_Title,      "title",      CSG, SIT_STR,  OFFSET(SIT_Widget, sw_Title) },
		{ SIT_SelColor,   "selColor",   _SG, SIT_LONG, OFFSET(SIT_ColorDialog, sc_Color) },
		{ SIT_TagEnd }
	};

	TagList FontDialogClass[] = {
		{ SIT_Title,      "title",      CSG, SIT_STR,  OFFSET(SIT_Widget, sw_Title) },
		{ SIT_FixedFont,  "fixedFont",  _SG, SIT_BOOL, OFFSET(SIT_FontDialog, sf_FixedFont) },
		{ SIT_SelFont,    "selFont",    _SG, SIT_STR,  OFFSET(SIT_FontDialog, sf_SelFont) },
		{ SIT_TagEnd }
	};

	TagList FolderDialogClass[] = {
		{ SIT_Title,      "title",      _SG, SIT_STR, OFFSET(SIT_Widget, sw_Title) },
		{ SIT_InitPath,   "initPath",   _SG, SIT_STR, OFFSET(SIT_FolderSel, sf_InitPath) },
		{ SIT_TagEnd }
	};

	static int SIT_ManageFileDialog(SIT_Widget, APTR, APTR);
	static int SIT_ManageColorDialog(SIT_Widget, APTR, APTR);
	static int SIT_ManageFontDialog(SIT_Widget, APTR, APTR);
	static int SIT_ManageFolderDialog(SIT_Widget, APTR, APTR);

/*
 * Initialize a file dialog. There no Win32 API involved yet, only when dialog
 * will be managed, using GetOpenFileName().
 */
static int SIT_SetFileValues(SIT_Widget w, APTR cd, APTR ud)
{
	SIT_Variant *  value = ud;
	SIT_FileDialog dlg   = (APTR) w;

	if (((TagList *)cd)->tl_TagID == SIT_InitPath)
	{
		STRPTR p, base;
		if (dlg->sf_InitPath)
		{
			p = alloca(StrLenM(dlg->sf_InitPath, value->string, NULL) + 5);
			strcpy(p, dlg->sf_InitPath);
			base = BaseName(p);
			if (*base) ParentDir(p);
			AddPart(p, value->string, 1<<20);
			free(dlg->sf_InitPath);
			dlg->sf_InitPath = realloc(value->string, strlen(p) + 1);
			strcpy(dlg->sf_InitPath, p);
		}
		else dlg->sf_InitPath = value->string;
		for (p = dlg->sf_InitPath; *p; p ++)
			if (*p == '/') *p = '\\';
		return 1;
	}
	else return SIT_SetWidgetValue(w, cd, ud);
}

BOOL SIT_CreateFileDialog(SIT_Widget w, va_list args)
{
	/* Yep, that's all */
	w->sw_SetValue = SIT_SetFileValues;
	SIT_ParseTags(w, args, w->sw_Class = FileDialogClass);
	w->sw_Flags  = SITF_TopLevel;
	w->sw_Manage = SIT_ManageFileDialog;
	return TRUE;
}

BOOL SIT_CreateColorDialog(SIT_Widget w, va_list args)
{
	SIT_ColorDialog c = (SIT_ColorDialog) w;
	c->sc_OldColor = c->sc_Color = -1;
	SIT_ParseTags(w, args, w->sw_Class = ColorDialogClass);
	w->sw_Flags  = SITF_TopLevel;
	w->sw_Manage = SIT_ManageColorDialog;
	return TRUE;
}

BOOL SIT_CreateFontDialog(SIT_Widget w, va_list args)
{
	SIT_ParseTags(w, args, w->sw_Class = FontDialogClass);
	w->sw_Flags  = SITF_TopLevel;
	w->sw_Manage = SIT_ManageFontDialog;
	return TRUE;
}

BOOL SIT_CreateFolderDialog(SIT_Widget w, va_list args)
{
	SIT_ParseTags(w, args, w->sw_Class = FolderDialogClass);
	w->sw_Flags  = SITF_TopLevel;
	w->sw_Manage = SIT_ManageFolderDialog;
	return TRUE;
}

static int AddPartWtoMB(STRPTR buffer, int max, LPWSTR part, Bool dir)
{
	int sz = UTF16ToUTF8(buffer, max, (STRPTR) part, -1);

	if (sz < 0 || ! dir) return sz; buffer += sz-2;
	if (*buffer != '\\' && *buffer != '/') buffer[1] = '\\';
	else sz --;
	return sz;
}

static int SIT_ManageFileDialog(SIT_Widget w, APTR cd, APTR ud)
{
	SIT_FileDialog dlg = (SIT_FileDialog) w;
	BOOL ret;

	memset(&dlg->ofn, 0, sizeof dlg->ofn);
	dlg->ofn.lStructSize = sizeof dlg->ofn;
	dlg->ofn.hwndOwner   = SIT_GetContainer(w);
	dlg->ofn.lpstrFile   = dlg->sf_FileName;
	dlg->ofn.nMaxFile    = DIM(dlg->sf_FileName);
	dlg->ofn.Flags       = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
	dlg->sf_FilePtr      = (STRPTR) dlg->sf_FileName;
	dlg->sf_FileName[0]  = 0;

	if (dlg->sf_Flags & SITV_FileMultiSelect)   dlg->ofn.Flags |= OFN_ALLOWMULTISELECT;
	if (dlg->sf_Flags & SITV_FileAskCreate)     dlg->ofn.Flags |= OFN_CREATEPROMPT;
	if (dlg->sf_Flags & SITV_FileMustExist)     dlg->ofn.Flags |= OFN_FILEMUSTEXIST;
	if (dlg->sf_Flags & SITV_FileWarnOverwrite) dlg->ofn.Flags |= OFN_OVERWRITEPROMPT;

	/* Title of dialog */
	if (IsDef(w->sw_Title))
		UTF8ToUTF16(w->sw_Title, dlg->ofn.lpstrTitle);

	/* Parse filter string */
	if (dlg->sf_Filters)
	{
		int len = MultiByteToWideChar(CP_UTF8, 0, dlg->sf_Filters, -1, NULL, 0) + 2;
		LPWSTR p, filters = alloca(len * sizeof *filters);

		MultiByteToWideChar(CP_UTF8, 0, dlg->sf_Filters, -1, filters, len);
		memset(filters + len - 2, 0, 2 * sizeof *filters);

		for (p = filters; *p; p ++) {
			if (*p == '\t' || *p == '\n') *p = 0;
			if (*p == '\r') wcscpy(p, p + 1);
		}
		dlg->ofn.lpstrFilter  = filters;
		dlg->ofn.nFilterIndex = dlg->sf_SelFilter + 1;
	}

	/* Initial dir and file */
	if (dlg->sf_InitPath)
	{
		STRPTR base = BaseName(dlg->sf_InitPath);

		MultiByteToWideChar(CP_UTF8, 0, base, -1, dlg->sf_FileName, DIM(dlg->sf_FileName));

		if (base > dlg->sf_InitPath)
		{
			TEXT ch = base[-1];
			base[-1] = 0;
			UTF8ToUTF16(dlg->sf_InitPath, dlg->ofn.lpstrInitialDir);
			base[-1] = ch;
		}
	}
	if (dlg->sf_Flags & SITV_FileSave)
		ret = pGetSaveFileName(&dlg->ofn);
	else
		ret = pGetOpenFileName(&dlg->ofn);

	if (ret)
	{
		/* User selected one or more files: convert from UTF16 to UTF8 */
		LPWSTR p, s;
		STRPTR out = (STRPTR) dlg->sf_FileName;
		int    len, sz, i;

		for (p = dlg->sf_FileName, len = 0; *p; ++len, p = wcschr(p, 0) + 1);
		if (! (dlg->sf_Flags & SITV_FileMultiSelect))
			len = 1;

		sz = (STRPTR) p - (STRPTR) dlg->sf_FileName;
		s = alloca(sz);
		memcpy(s, dlg->sf_FileName, sz);
		dlg->sf_NbSelect = (len == 1 ? 1 : len - 1);
		if (dlg->ofn.nFilterIndex > 0)
			dlg->sf_SelFilter = dlg->ofn.nFilterIndex - 1;

		/* Append first file selected */
		for (i = sz = 0, p = s; i < len; i ++)
		{
			if (i > 1)
				sz += AddPartWtoMB(out + sz, sizeof dlg->sf_FileName - sz, s, True);
			sz += AddPartWtoMB(out + sz, sizeof dlg->sf_FileName - sz, p, i == 0 && len > 1);
			p = wcschr(p, 0) + 1;
		}
		/* Keep last selected directory */
		SIT_SetValues(w, SIT_InitPath, dlg->sf_FileName, NULL);

		return True;
	}
	else /* User cancelled */
	{
		dlg->sf_NbSelect = 0;
		return False;
	}
}

/* Need to finish initialization in callback :-/ */
static UINT APIENTRY SIT_PostInitColor(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (msg == WM_INITDIALOG)
	{
		LPCHOOSECOLOR cc = (LPCHOOSECOLOR) lParam;
		SIT_Widget w = (SIT_Widget) cc->lCustData;
		RECT rect;
		int  x, y;

		/* Place dialog just over the parent */
		GetWindowRect(SIT_GetContainer(w), &rect);
		x = rect.left + GetSystemMetrics(SM_CXFRAME);
		y = rect.top  + GetSystemMetrics(SM_CYCAPTION);

		GetWindowRect(wnd, &rect);
		MoveWindow(wnd, x, y, rect.right - rect.left, rect.bottom - rect.top, FALSE);

		/* Change title if user has provided something */
		if (IsDef(w->sw_Title))
		{
			LPWSTR utf16;
			UTF8ToUTF16(w->sw_Title, utf16);
			SetWindowText(wnd, utf16);
		}
	}
	return 0;
}


/* Display a color selection dialog */
static int SIT_ManageColorDialog(SIT_Widget w, APTR cd, APTR ud)
{
	#define	WHITE     RGB(255,255,255)
	static COLORREF custcolors[16] = {
		WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE,
		WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE
	};
	#undef WHITE

	SIT_ColorDialog c = (SIT_ColorDialog) w;

	c->cc.lStructSize  = sizeof c->cc;
	c->cc.hwndOwner    = SIT_GetContainer(w);
	c->cc.lpCustColors = custcolors;
	c->cc.Flags        = CC_ENABLEHOOK | CC_FULLOPEN;
	c->cc.lCustData    = (LPARAM) w;
	c->cc.lpfnHook     = SIT_PostInitColor;

	if (c->sc_Color >= 0 || c->sc_OldColor >= 0)
	{
		c->cc.rgbResult = c->sc_Color >= 0 ? c->sc_Color : c->sc_OldColor;
		c->cc.Flags    |= CC_RGBINIT;
 	}

	if (pChooseColor(&c->cc))
	{
		c->sc_Color = c->sc_OldColor = c->cc.rgbResult;
		return True;
	}
	c->sc_Color = -1;
	return False;
}

/* Font chooser dialog */
static int SIT_ManageFontDialog(SIT_Widget w, APTR cd, APTR ud)
{
	SIT_FontDialog fd = (APTR) w;

	memset(&fd->cf, 0, sizeof fd->cf);
	fd->cf.lStructSize = sizeof fd->cf;
	fd->cf.hwndOwner   = SIT_GetContainer(w);
	fd->cf.Flags       = CF_INITTOLOGFONTSTRUCT | CF_EFFECTS | CF_SCREENFONTS;
	fd->cf.lpLogFont   = &fd->lf;

	if (IsDef(fd->sf_SelFont))
		GFX_FillLogFont(fd->sf_SelFont, &fd->lf);
	else
		GetObject(w->sw_Parent->sw_Font, sizeof fd->lf, &fd->lf);

	if (fd->sf_FixedFont) fd->cf.Flags |= CF_FIXEDPITCHONLY;

	if (pChooseFont(&fd->cf))
	{
		int    len = WideCharToMultiByte(CP_UTF8, 0, fd->lf.lfFaceName, -1, 0, 0, 0, 0) + 32;
		STRPTR fmt = malloc(len);

		WideCharToMultiByte(CP_UTF8, 0, fd->lf.lfFaceName, -1, fmt, len, 0, 0);
		if (fd->lf.lfWeight >= 700) strcat(fmt, "/bold");
		if (fd->lf.lfItalic)        strcat(fmt, "/italic");
		if (fd->lf.lfUnderline)     strcat(fmt, "/underline");
		if (fd->lf.lfStrikeOut)     strcat(fmt, "/strike");

		if (fd->sf_SelFont) free(fd->sf_SelFont);
		fd->sf_SelFont = fmt;
		return True;
	}
	return False;
}

/*
 * Yep, all this cruft is just to get the full path name from SHBrowseForFolder.
 * What a mess (quoted from MSDN, sigh).
 */
static HRESULT SHGetUIObjectFromFullPIDL(LPCITEMIDLIST pidl, HWND hwnd, REFIID riid, void **ppv)
{
	LPCITEMIDLIST pidlChild;
	IShellFolder* psf;
	*ppv = NULL;
	HRESULT hr = SHBindToParent(pidl, &IID_IShellFolder, (APTR) &psf, &pidlChild);
	if (SUCCEEDED(hr))
	{
		hr = psf->lpVtbl->GetUIObjectOf(psf, hwnd, 1, &pidlChild, riid, NULL, ppv);
		psf->lpVtbl->Release(psf);
	}
	return hr;
}

/* Doesn't seem to be defined in MinGW headers ... */
#ifndef	ILNext
#define ILSkip(pidl, cb)       ((LPITEMIDLIST)(((BYTE*)(pidl))+cb))
#define ILNext(pidl)           ILSkip(pidl, (pidl)->mkid.cb)
#endif

static HRESULT SHILClone(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidl)
{
	DWORD cbTotal = 0;
	if (pidl)
	{
		LPCITEMIDLIST pidl_temp = pidl;
		cbTotal += pidl_temp->mkid.cb;
		while (pidl_temp->mkid.cb)
		{
			cbTotal += pidl_temp->mkid.cb;
			pidl_temp = ILNext(pidl_temp);
		}
	}

	*ppidl = (LPITEMIDLIST) pCoTaskMemAlloc(cbTotal);

	if (*ppidl)
		CopyMemory(*ppidl, pidl, cbTotal);

	return *ppidl ? S_OK: E_OUTOFMEMORY;
}

/*
 * Get the target PIDL for a folder PIDL. This also deals with cases of a folder
 * shortcut or an alias to a real folder.
 */
static HRESULT SHGetTargetFolderIDList(LPCITEMIDLIST pidlFolder, LPITEMIDLIST *ppidl)
{
	IShellLink *psl;

	*ppidl = NULL;

	HRESULT hr = SHGetUIObjectFromFullPIDL(pidlFolder, NULL, &IID_IShellLink, (APTR) &psl);

	if (SUCCEEDED(hr))
	{
		hr = psl->lpVtbl->GetIDList(psl, ppidl);
		psl->lpVtbl->Release(psl);
	}

	/* It's not a folder shortcut so get the PIDL normally */
	if (FAILED(hr))
		hr = SHILClone(pidlFolder, ppidl);

	return hr;
}

/*
 * Get the target folder for a folder PIDL. This deals with cases where a folder
 * is an alias to a real folder, folder shortcuts, the My Documents folder, etc.
 */
STDAPI SHGetTargetFolderPath(LPCITEMIDLIST pidlFolder, LPWSTR pszPath)
{
	LPITEMIDLIST pidlTarget;

	*pszPath = 0;
	HRESULT hr = SHGetTargetFolderIDList(pidlFolder, &pidlTarget);

	if (SUCCEEDED(hr))
	{
		/*
		 * Adding insult to injury, with all this shit, we are still limited to
		 * MAX_PATH (260) characters. And you wonder why people thinks C is a
		 * poor language with such shitty API :-(
		 */
		SHGetPathFromIDListW(pidlTarget, pszPath);
		pCoTaskMemFree(pidlTarget);
	}

	return *pszPath ? S_OK : E_FAIL;
}

/* Preselect user directory */
static int CALLBACK SIT_PostInitBrowse(HWND wnd, UINT msg, LPARAM lParam, LPARAM lpData)
{
	if (msg == BFFM_INITIALIZED)
	{
		SIT_FolderSel f = (APTR) lpData;
		LPWSTR dir;

		UTF8ToUTF16(f->sf_InitPath, dir);

		SendMessage(wnd, BFFM_SETSELECTION, TRUE, (LPARAM) dir);

		/* Set focus to tree view */
		SetFocus(FindWindowEx(wnd, NULL, L"SysTreeView32", NULL));
	}
	return 0;
}

/* Std folder chooser dialog initialization */
static int SIT_ManageFolderDialog(SIT_Widget w, APTR cd, APTR ud)
{
	SIT_FolderSel f = (APTR) w;

	memset(&f->bi, 0, sizeof f->bi);
	f->bi.hwndOwner      = SIT_GetContainer(w);
	f->bi.pszDisplayName = f->path;
	f->bi.lpfn           = SIT_PostInitBrowse;
	f->bi.lParam         = (LPARAM) w;
	f->bi.ulFlags        = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;

	if (IsDef(w->sw_Title))
		UTF8ToUTF16(w->sw_Title, f->bi.lpszTitle);

	ITEMIDLIST * pidl = SHBrowseForFolder(&f->bi);

	if (pidl)
	{
		SHGetTargetFolderPath(pidl, f->path);
		pCoTaskMemFree(pidl);
		int len = WideCharToMultiByte(CP_UTF8, 0, f->path, -1, NULL, 0, NULL, NULL);

		if (f->sf_InitPath) free(f->sf_InitPath);
		f->sf_InitPath = malloc(len);
		WideCharToMultiByte(CP_UTF8, 0, f->path, -1, f->sf_InitPath, len, NULL, NULL);

		return True;
	}
	return False;
}
